﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="CAEXObjectCollectionViewModel.cs" >
//   J. Prinz
// </copyright>
// <summary>
//   Representation Class for a Collection of CAEXObjects
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace AutomationMLMapper.ViewModels.CAEXObjectCollectionViewModels
{
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Windows.Data;
    using System.Windows.Input;

    using AMLEngineExtensions;
    using AutomationMLMapper.MVVM_Framework;

    /// <summary>
    /// Representation Class for a Collection of CAEXObjects
    /// </summary>
    /// <typeparam name="T">
    /// Any Object which implements <see cref="ICAEXObject"/>
    /// </typeparam>
    public class CAEXObjectCollectionViewModel<T> : CollectionViewSource, IViewModel, INotifyPropertyChanged
        where T : ICAEXObject
    {
        #region Fields

        /// <summary>
        ///   <see cref="AddCommand"/>
        /// </summary>
        private RelayCommand<object> addCommand;

        /// <summary>
        ///  <see cref="AllowAdd"/>
        /// </summary>
        private bool allowAdd;

        /// <summary>
        ///  <see cref="AllowDelete"/>
        /// </summary>
        private bool allowDelete;

        /// <summary>
        ///  <see cref="AllowEditElement"/>
        /// </summary>
        private bool allowEditElement;

        /// <summary>
        ///  <see cref="DeleteCommand"/>
        /// </summary>
        private RelayCommand<object> deleteCommand;

        /// <summary>
        ///  <see cref="IsExpanded"/>
        /// </summary>
        private bool isExpanded;

        #endregion
        
        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="CAEXObjectCollectionViewModel{T}"/> class for a defined Parent.
        /// </summary>
        /// <param name="parentNode">
        /// Parent of the Collection, this is the ViewModelInterface of the CaexObject which contains the CAEXObjects, 
        /// represented with this CollectionViewModel as Sub-Nodes. 
        /// </param>
        public CAEXObjectCollectionViewModel(ICAEXObject parentNode)
        {
            this.Elements = new ObservableCollection<T>();
            this.Source = this.Elements;
            this.IsExpanded = true;
            this.Parent = parentNode;
             
            this.AllowAdd = true;
            this.AllowDelete = true;
            this.AllowEditElement = true;
            
            this.Elements.CollectionChanged += this.ElementsCollectionChanged;
        }


        #endregion

        #region Delegates

        /// <summary>
        ///   CommandHandler for all events, registered with Command Execution
        /// </summary>
        /// <param name="sender"> the sender </param>
        /// <param name="commandArgs"> Event arguments containing information about the active item </param>
        public delegate void CommandHandler(object sender, CollectionCommandEventArgs<T> commandArgs);

        #endregion

        #region Public Events

        /// <summary>
        ///   Event raised after execution of any command
        /// </summary>
        public event CommandHandler AfterExecuteCommand;

        /// <summary>
        ///   Event raised before checking executability of any command
        /// </summary>
        public event CommandHandler AllowCommand;

        /// <summary>
        ///   Event raised before execution of any command
        /// </summary>
        public event CommandHandler BeforeExecuteCommand;

        /// <summary>
        ///   Event raised on a Property Change of any puplic property
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Public Properties

        /// <summary>
        ///   add an element to the collection and also create the CaexObject
        /// </summary>
        public ICommand AddCommand
        {
            get
            {
                return this.addCommand
                       ??
                       (this.addCommand = new RelayCommand<object>(this.AddCommandExecute, this.AddCommandCanExecute));
            }
        }

        /// <summary>
        ///   Allow Adding Elements to the Collection
        /// </summary>
        public bool AllowAdd
        {
            get
            {
                return this.allowAdd;
            }

            set
            {
                this.allowAdd = value;
                this.OnNotifyPropertyChanged("AllowAdd");
            }
        }

        /// <summary>
        ///   Allow Deleting Elements from the Collection
        /// </summary>
        public bool AllowDelete
        {
            get
            {
                return this.allowDelete;
            }

            set
            {
                this.allowDelete = value;
                this.OnNotifyPropertyChanged("AllowDelete");
            }
        }

        /// <summary>
        ///   Allow Editing of Elements in the collection
        /// </summary>
        public bool AllowEditElement
        {
            get
            {
                return this.allowEditElement;
            }

            set
            {
                this.allowEditElement = value;
                this.OnNotifyPropertyChanged("AllowEditElement");
            }
        }

        /// <summary>
        ///   delte the current element from the collection and also its CaexObject
        /// </summary>
        public ICommand DeleteCommand
        {
            get
            {
                return this.deleteCommand
                       ??
                       (this.deleteCommand =
                        new RelayCommand<object>(this.DeleteCommandExecute, this.DeleteCommandCanExecute));
            }
        }

        /// <summary>
        ///   The DisplayName of the Collection
        /// </summary>
        public string DisplayName
        {
            get
            {
                return "CAEXObjects";
            }
        }

        /// <summary>
        ///   The DisplayName of the ElementType of the Collection
        /// </summary>
        public string ElementDisplayName
        {
            get
            {
                var parameterType = typeof(T);
                var prop = parameterType.GetProperty("DisplayName");

                if (prop != null)
                {
                    return prop.GetValue(null, null) as string;
                }

                return string.Empty;
            }
        }

        /// <summary>
        ///   The Elements in the Collection which are CAEXObjectViewModels"/>
        /// </summary>
        public ObservableCollection<T> Elements { get; private set; }

        /// <summary>
        ///   Sate of the Expander which shows the Representation of the Objects in the collection, when expanded
        /// </summary>
        public bool IsExpanded
        {
            get
            {
                return this.isExpanded;
            }

            set
            {
                this.isExpanded = value;
                this.OnNotifyPropertyChanged("IsExpanded");
            }
        }

        /// <summary>
        ///   The CaexObjectViewModel Interface which has populated the Collection with CaexObjects
        /// </summary>
        public ICAEXObject Parent { get; set; }

        #endregion

        #region Methods

        /// <summary>
        /// PropertyChange Notification
        /// </summary>
        /// <param name="propertyName">
        /// the name of the property.
        /// </param>
        protected void OnNotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// The handler for a PropertyChange-Event of any Item in the collection. 
        /// This Handler is intented to be used for the implementation of Undo and Redo
        /// </summary>
        /// <param name="sender">
        /// The sender is any object which implements the CaexObject - Interface <see cref="ICAEXObject"/>. 
        /// </param>
        /// <param name="e">
        /// the Property Chancge Information 
        /// </param>
        private static void CaexVisualItemPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var caexObject = sender as ICAEXObject;
            if (caexObject != null)
            {
                // TODO Implementation of Undo / Redo
            }
        }

        /// <summary>
        /// Test, if adding items is allowed.
        /// </summary>
        /// <param name="parameter">
        /// parameter is unused.
        /// </param>
        /// <returns>
        /// true, if command is enabled.
        /// </returns>
        private bool AddCommandCanExecute(object parameter)
        {
            return (this.Parent != null && this.Parent.GetCaexObject() != null) && this.AllowAdd
                   && this.OnCanExecuteCommand(this.addCommand);
        }
       
        /// <summary>
        /// Execute Action for the <see cref="AddCommand"/>
        /// </summary>
        /// <param name="parameter">
        /// unused parameter. 
        /// </param>
        private void AddCommandExecute(object parameter)
        {
            if (this.OnBeforeExecuteCommand(this.addCommand))
            {
                var newCaexObjectRepresentation = this.Parent.AddElement(typeof(T));
                if (newCaexObjectRepresentation != null)
                {
                    this.Elements.Add((T)newCaexObjectRepresentation);
                    newCaexObjectRepresentation.LoadCaexToRepresentationModel();
                }

                this.OnAfterExecuteCommand(this.addCommand);
                this.IsExpanded = true;
            }
        }
        
        /// <summary>
        /// Test, if <see cref="DeleteCommand"/> can execute. 
        /// <see cref="AllowDelete"/> has to be true and an item has to be selected in the view
        /// </summary>
        /// <param name="parameter">
        /// unused parameter. 
        /// </param>
        /// <returns>
        /// true, if command can execute
        /// </returns>
        private bool DeleteCommandCanExecute(object parameter)
        {
            return this.AllowDelete && this.View.CurrentItem != null && this.OnCanExecuteCommand(this.deleteCommand);
        }

        /// <summary>
        /// Execute Action of the <see cref="DeleteCommand"/>
        /// </summary>
        /// <param name="parameter">
        /// unused parameter. 
        /// </param>
        private void DeleteCommandExecute(object parameter)
        {
            if (this.OnBeforeExecuteCommand(this.deleteCommand))
            {
                var element = (T)this.View.CurrentItem;
                if (!element.Equals(default(T)))
                {
                    var caex = element.GetCaexObject();
                    if (caex != null)
                    {
                        caex.Remove();
                    }

                    this.Elements.Remove(element);
                }
            }
            this.OnAfterExecuteCommand(this.deleteCommand);
        }

        /// <summary>
        /// Changes in the Collection are recognized to manage the State of the UndoRedo Manager 
        /// TODO Implementation of undo / redo
        /// </summary>
        /// <param name="sender">
        /// Changed collection
        /// </param>
        /// <param name="e">
        /// Collection Change Event Arguments
        /// </param>
        private void ElementsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (INotifyPropertyChanged caexVisualItem in e.NewItems)
                    {
                        caexVisualItem.PropertyChanged += CaexVisualItemPropertyChanged;
                    }

                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (INotifyPropertyChanged caexVisualItem in e.OldItems)
                    {
                        caexVisualItem.PropertyChanged -= CaexVisualItemPropertyChanged;
                    }

                    break;
            }
        }

        /// <summary>
        /// All ..Execute Methods of all commands call this method to raise an event after execution
        /// </summary>
        /// <param name="command">
        /// The command, which raised the event and is passed to any EventHandler in the EventArgument
        /// </param>
        private void OnAfterExecuteCommand(ICommand command)
        {
            if (this.AfterExecuteCommand != null)
            {
                var commandEventArgs = new CollectionCommandEventArgs<T>
                    {
                       Command = command, ActiveItem = (T)this.View.CurrentItem 
                    };

                this.AfterExecuteCommand(this, commandEventArgs);
            }
        }

        /// <summary>
        /// All ..Execute Methods of all commands call this method to raise an event prior to the execution
        /// </summary>
        /// <param name="command">
        /// The command, which raised the event and is passed to any EventHandler in the EventArgument
        /// </param>
        /// <returns>
        /// The System.Boolean.
        /// </returns>
        private bool OnBeforeExecuteCommand(ICommand command)
        {
            if (this.BeforeExecuteCommand != null)
            {
                var commandEventArgs = new CollectionCommandEventArgs<T>
                    {
                       Command = command, ActiveItem = (T)this.View.CurrentItem 
                    };

                this.BeforeExecuteCommand(this, commandEventArgs);
                return !commandEventArgs.CancelCommand;
            }

            return true;
        }

        /// <summary>
        /// All ..CanExecute Methods of all commands call this method to raise an event prior to the checking
        /// </summary>
        /// <param name="command">
        /// The command, which raised the event and is passed to any EventHandler in the EventArgument
        /// </param>
        /// <returns>
        /// The System.Boolean.
        /// </returns>
        private bool OnCanExecuteCommand(ICommand command)
        {
            if (this.AllowCommand != null)
            {
                var commandEventArgs = new CollectionCommandEventArgs<T>
                    {
                       Command = command, ActiveItem = (T)this.View.CurrentItem 
                    };

                this.AllowCommand(this, commandEventArgs);
                return commandEventArgs.AllowCommand;
            }

            return true;
        }

        #endregion
    }

    #region CollectionCommandEventArgs

    #endregion
}